/****************************************************************************
 * Copyright (c) 2009 Composent, Inc. and others.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * Contributors:
 *    Composent, Inc. - initial API and implementation
 *    Markus Alexander Kuppe (ecf-dev_eclipse.org <at> lemmster <dot> de) - Reworked completely
 *
 * SPDX-License-Identifier: EPL-2.0
 *****************************************************************************/

package org.eclipse.ecf.tests.discovery;


import java.util.Arrays;

import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.ecf.core.ContainerConnectException;
import org.eclipse.ecf.core.events.IContainerEvent;
import org.eclipse.ecf.core.util.ECFRuntimeException;
import org.eclipse.ecf.discovery.IServiceEvent;
import org.eclipse.ecf.discovery.IServiceInfo;
import org.eclipse.ecf.discovery.identity.IServiceTypeID;
import org.eclipse.ecf.tests.discovery.listener.TestServiceListener;
import org.eclipse.ecf.tests.discovery.listener.TestServiceTypeListener;
import org.eclipse.equinox.concurrent.future.IFuture;

public abstract class DiscoveryTest extends AbstractDiscoveryTest {

	protected long waitTimeForProvider = 1000;
	protected int eventsToExpect = 1;
	
	public DiscoveryTest(String name) {
		super(name);
	}
	
	protected void setWaitTimeForProvider(long aWaitTimeForProvider) {
		this.waitTimeForProvider = aWaitTimeForProvider + (aWaitTimeForProvider * 1 / 2);
	}

	protected void registerService(IServiceInfo serviceInfo) throws Exception {
		assertNotNull(serviceInfo);
		assertNotNull(discoveryAdvertiser);
		discoveryAdvertiser.registerService(serviceInfo);
	}

	protected void unregisterService(IServiceInfo serviceInfo) throws Exception {
		assertNotNull(serviceInfo);
		assertNotNull(discoveryAdvertiser);
		discoveryAdvertiser.unregisterService(serviceInfo);
	}

	/*
	 * (non-Javadoc)
	 * @see junit.framework.TestCase#tearDown()
	 */
	protected void tearDown() throws Exception {
		discoveryAdvertiser.unregisterService(serviceInfo);
		discoveryLocator.purgeCache();
		super.tearDown();
	}

	protected void registerService() {
		try {
			discoveryAdvertiser.registerService(serviceInfo);
		} catch (final ECFRuntimeException e) {
			fail("IServiceInfo may be valid with this IDCA " + e.getMessage());
		}
	}

	protected void unregisterService() {
		try {
			discoveryAdvertiser.unregisterService(serviceInfo);
		} catch (final ECFRuntimeException e) {
			fail("unregistering of " + serviceInfo + " should just work");
		}
	}

	protected void addListenerRegisterAndWait(TestServiceListener testServiceListener, IServiceInfo aServiceInfo) {
		synchronized (testServiceListener) {
			// register a service which we expect the test listener to get notified of
			registerService();
			try {
				testServiceListener.wait(waitTimeForProvider);
			} catch (final InterruptedException e) {
				Thread.currentThread().interrupt();
				fail("Some discovery unrelated threading issues?");
			}
		}
	}

	protected void addServiceListener(TestServiceListener serviceListener) {
		discoveryLocator.addServiceListener(serviceListener);
		addListenerRegisterAndWait(serviceListener, serviceInfo);
		discoveryLocator.removeServiceListener(serviceListener);
		IContainerEvent[] event = serviceListener.getEvent();
		assertNotNull("Test listener didn't receive any discovery event", event);
		assertEquals("Test listener received unexpected amount of discovery events: \n\t" + Arrays.asList(event), eventsToExpect, event.length);
		IServiceInfo serviceInfo2 = ((IServiceEvent) event[eventsToExpect - 1]).getServiceInfo();
		assertTrue("IServiceInfo should match, expected:\n\t" + serviceInfo + " but was \n\t" + serviceInfo2, comparator.compare(serviceInfo2, serviceInfo) == 0);
	}

	/**
	 * Test method for
	 * {@link org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#getServiceInfo(org.eclipse.ecf.discovery.identity.IServiceID)}.
	 * @throws ContainerConnectException 
	 */
	public void testGetServiceInfo() throws ContainerConnectException {
		registerService();
		final IServiceInfo info = discoveryLocator.getServiceInfo(serviceInfo.getServiceID());
		assertTrue("IServiceInfo should match, expected:\n\t" + serviceInfo + " but:\n\t" + info, comparator.compare(info, serviceInfo) == 0);
	}

	/**
	 * Test method for
	 * {@link org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#getServiceTypes()}.
	 * @throws ContainerConnectException 
	 */
	public void testGetServiceTypes() throws ContainerConnectException {
		registerService();
		final IServiceTypeID[] serviceTypeIDs = discoveryLocator.getServiceTypes();
		assertTrue("IServiceInfo[] is empty", serviceTypeIDs.length >= 1);
		for (int i = 0; i < serviceTypeIDs.length; i++) {
			IServiceTypeID iServiceTypeId = serviceTypeIDs[i];
			if(serviceInfo.getServiceID().getServiceTypeID().equals(iServiceTypeId)) {
				return;
			}
		}
		fail("Self registered service not found");
	}

	/**
	 * Test method for
	 * {@link org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#getServices()}.
	 * @throws ContainerConnectException 
	 */
	public void testGetServices() throws ContainerConnectException {
		registerService();
		final IServiceInfo[] services = discoveryLocator.getServices();
		assertTrue("IServiceInfo[] is empty", services.length >= 1);
		for (int i = 0; i < services.length; i++) {
			IServiceInfo iServiceInfo = services[i];
			if(comparator.compare(iServiceInfo, serviceInfo) == 0) {
				return;
			}
		}
		fail("Self registered service not found");
	}

	/**
	 * Test method for
	 * {@link org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#getServices(org.eclipse.ecf.discovery.identity.IServiceTypeID)}.
	 * @throws ContainerConnectException 
	 */
	public void testGetServicesIServiceTypeID() throws ContainerConnectException {
		registerService();
		final IServiceInfo serviceInfos[] = discoveryLocator.getServices(serviceInfo.getServiceID().getServiceTypeID());
		assertTrue("IServiceInfo[] is empty", serviceInfos.length >= 1);
		for (int i = 0; i < serviceInfos.length; i++) {
			IServiceInfo iServiceInfo = serviceInfos[i];
			if(comparator.compare(iServiceInfo, serviceInfo) == 0) {
				return;
			}
		}
		fail("Self registered service not found");
	}

	/**
	 * Test method for
	 * {@link org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#registerService(org.eclipse.ecf.discovery.IServiceInfo)}.
	 * @throws ContainerConnectException 
	 */
	public void testRegisterService() throws ContainerConnectException {
		registerService();
		final IServiceInfo[] services = discoveryLocator.getServices();
		assertTrue("IServiceInfo[] is empty", services.length >= 1);
		for (int i = 0; i < services.length; i++) {
			final IServiceInfo service = services[i];
			if (comparator.compare(service, serviceInfo) == 0) {
				return;
			}
		}
		fail("Self registered service not found");
	}

	/**
	 * Test method for
	 * {@link org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#unregisterService(org.eclipse.ecf.discovery.IServiceInfo)}.
	 * @throws ContainerConnectException 
	 */
	public void testUnregisterService() throws ContainerConnectException {
		testRegisterService();
		unregisterService();
		final IServiceInfo[] services = discoveryLocator.getServices();
		for (int i = 0; i < services.length; i++) {
			final IServiceInfo service = services[i];
			if (comparator.compare(service, serviceInfo) == 0) {
				fail("Expected service to be not registered anymore");
			}
		}
	}

	/**
	 * Test method for
	 * {@link org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#addServiceListener(org.eclipse.ecf.discovery.IServiceListener)}.
	 * @throws ContainerConnectException 
	 */
	public void testAddServiceListenerIServiceListener() throws ContainerConnectException {
		IServiceInfo[] services = discoveryLocator.getServices();
		assertTrue("No Services must be registerd at this point " + (services.length == 0 ? "" : services[0].toString()), services.length == 0);

		final TestServiceListener tsl = new TestServiceListener(eventsToExpect, discoveryLocator, getName(), getTestId());
		addServiceListener(tsl);
	}

	/**
	 * Test method for
	 * {@link org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#addServiceListener(org.eclipse.ecf.discovery.identity.IServiceTypeID, org.eclipse.ecf.discovery.IServiceListener)}.
	 * @throws ContainerConnectException 
	 */
	public void testAddServiceListenerIServiceTypeIDIServiceListener() throws ContainerConnectException {
		IServiceInfo[] services = discoveryLocator.getServices();
		assertTrue("No Services must be registerd at this point " + (services.length == 0 ? "" : services[0].toString()), services.length == 0);

		final TestServiceListener tsl = new TestServiceListener(eventsToExpect, discoveryLocator, getName(), getTestId());
		discoveryLocator.addServiceListener(serviceInfo.getServiceID().getServiceTypeID(), tsl);
		addListenerRegisterAndWait(tsl, serviceInfo);
		discoveryLocator.removeServiceListener(serviceInfo.getServiceID().getServiceTypeID(), tsl);
		
		IContainerEvent[] event = tsl.getEvent();
		assertNotNull("Test listener didn't receive discovery", event);
		assertEquals("Test listener received unexpected amount of discovery events: \n\t" + Arrays.asList(event), eventsToExpect, event.length);
		IServiceInfo serviceInfo2 = ((IServiceEvent) event[eventsToExpect - 1]).getServiceInfo();
		assertTrue("IServiceInfo should match, expected:\n\t" + serviceInfo + " but:\n\t" + serviceInfo2, comparator.compare(serviceInfo2, serviceInfo) == 0);
	}

	/**
	 * Test method for
	 * {@link org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#addServiceTypeListener(org.eclipse.ecf.discovery.IServiceTypeListener)}.
	 * @throws ContainerConnectException 
	 */
	public void testAddServiceTypeListener() throws ContainerConnectException {
		IServiceInfo[] services = discoveryLocator.getServices();
		assertTrue("No Services must be registerd at this point " + (services.length == 0 ? "" : services[0].toString()), services.length == 0);

		final TestServiceTypeListener testTypeListener = new TestServiceTypeListener(eventsToExpect);
		discoveryLocator.addServiceTypeListener(testTypeListener);

		synchronized (testTypeListener) {
			// register a service which we expect the test listener to get notified of
			registerService();
			try {
				testTypeListener.wait(waitTimeForProvider);
			} catch (final InterruptedException e) {
				Thread.currentThread().interrupt();
				fail("Some discovery unrelated threading issues?");
			}
		}
		
		discoveryLocator.removeServiceTypeListener(testTypeListener);
		
		IContainerEvent[] event = testTypeListener.getEvent();
		assertNotNull("Test listener didn't receive discovery", event);
		assertEquals("Test listener received unexpected amount of discovery events: \n\t" + Arrays.asList(event), eventsToExpect, event.length);
	}
	

	/**
	 * Test method for
	 * {@link org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#removeServiceListener(org.eclipse.ecf.discovery.IServiceListener)}.
	 * @throws ContainerConnectException 
	 */
	public void testRemoveServiceListenerIServiceListener() throws ContainerConnectException {
		final TestServiceListener serviceListener = new TestServiceListener(eventsToExpect, discoveryLocator, getName(), getTestId());
		addServiceListener(serviceListener);
		//TODO reregister and verify the listener doesn't receive any events any longer.
	}

	/**
	 * Test method for
	 * {@link org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#removeServiceListener(org.eclipse.ecf.discovery.identity.IServiceTypeID, org.eclipse.ecf.discovery.IServiceListener)}.
	 * @throws ContainerConnectException 
	 */
	public void testRemoveServiceListenerIServiceTypeIDIServiceListener() throws ContainerConnectException {
		final TestServiceListener serviceListener = new TestServiceListener(eventsToExpect, discoveryLocator, getName(), getTestId());
		addServiceListener(serviceListener);
		//TODO reregister and verify the listener doesn't receive any events any longer.
	}

	/**
	 * Test method for
	 * {@link org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#removeServiceTypeListener(org.eclipse.ecf.discovery.IServiceTypeListener)}.
	 * @throws ContainerConnectException 
	 */
	public void testRemoveServiceTypeListener() throws ContainerConnectException {
		assertTrue("No Services must be registerd at this point", discoveryLocator.getServices().length == 0);

		final TestServiceTypeListener testTypeListener = new TestServiceTypeListener(eventsToExpect);
		discoveryLocator.addServiceTypeListener(testTypeListener);

		synchronized (testTypeListener) {
			// register a service which we expect the test listener to get notified of
			registerService();
			try {
				testTypeListener.wait(waitTimeForProvider);
			} catch (final InterruptedException e) {
				Thread.currentThread().interrupt();
				fail("Some discovery unrelated threading issues?");
			}
		}
		
		discoveryLocator.removeServiceTypeListener(testTypeListener);
		
		IContainerEvent[] event = testTypeListener.getEvent();
		assertNotNull("Test listener didn't receive any discovery event", event);
		assertEquals("Test listener received unexpected amount of discovery events: \n\t" + Arrays.asList(event), eventsToExpect, event.length);
		
		//TODO reregister and verify the listener doesn't receive any events any longer.
	}

	/**
	 * Test method for
	 * {@link org.eclipse.ecf.discovery.IDiscoveryLocator#getAsyncServiceInfo(org.eclipse.ecf.discovery.identity.IServiceID)}.
	 * @throws InterruptedException 
	 * @throws OperationCanceledException 
	 * @throws ContainerConnectException 
	 */
	public void testGetAsyncServiceInfo() throws OperationCanceledException, InterruptedException, ContainerConnectException {
		registerService();
		final IFuture aFuture = discoveryLocator.getAsyncServiceInfo(serviceInfo.getServiceID());
		final Object object = aFuture.get();
		assertTrue(object instanceof IServiceInfo);
		final IServiceInfo info = (IServiceInfo) object;
		assertTrue("IServiceInfo should match, expected:\n\t" + serviceInfo + " but:\n\t" + info, comparator.compare(info, serviceInfo) == 0);
	}
	
	/**
	 * Test method for
	 * {@link org.eclipse.ecf.discovery.IDiscoveryLocator#getAsyncServices()}.
	 * @throws ContainerConnectException 
	 * @throws InterruptedException 
	 * @throws OperationCanceledException 
	 */
	public void testGetAsyncServices() throws ContainerConnectException, OperationCanceledException, InterruptedException {
		registerService();
		final IFuture aFuture = discoveryLocator.getAsyncServices();
		final Object object = aFuture.get();
		assertTrue(object instanceof IServiceInfo[]);
		final IServiceInfo[] services = (IServiceInfo[]) object;
		assertTrue("Found: " + services.length + Arrays.asList(services), services.length == eventsToExpect);
		for (int i = 0; i < services.length; i++) {
			IServiceInfo iServiceInfo = services[i];
			if(comparator.compare(iServiceInfo, serviceInfo) == 0) {
				return;
			}
		}
		fail("Self registered service not found");
	}
	
	/**
	 * Test method for
	 * {@link org.eclipse.ecf.discovery.IDiscoveryLocator#getAsyncServices(org.eclipse.ecf.discovery.identity.IServiceTypeID)}.
	 * @throws ContainerConnectException 
	 * @throws InterruptedException 
	 * @throws OperationCanceledException 
	 */
	public void testGetAsyncServicesIServiceTypeID() throws ContainerConnectException, OperationCanceledException, InterruptedException {
		registerService();
		final IFuture aFuture = discoveryLocator.getAsyncServices(serviceInfo.getServiceID().getServiceTypeID());
		final Object object = aFuture.get();
		assertTrue(object instanceof IServiceInfo[]);
		final IServiceInfo[] services = (IServiceInfo[]) object;
		assertTrue("Found: " + services.length + Arrays.asList(services), services.length == eventsToExpect);
		for (int i = 0; i < services.length; i++) {
			IServiceInfo iServiceInfo = services[i];
			if(comparator.compare(iServiceInfo, serviceInfo) == 0) {
				return;
			}
		}
		fail("Self registered service not found");
	}
	
	/**
	 * Test method for
	 * {@link org.eclipse.ecf.discovery.IDiscoveryLocator#getAsyncServiceTypes()}.
	 * @throws ContainerConnectException 
	 * @throws InterruptedException 
	 * @throws OperationCanceledException 
	 */
	public void testGetAsyncServiceTypes() throws ContainerConnectException, OperationCanceledException, InterruptedException {
		registerService();
		final IFuture aFuture = discoveryLocator.getAsyncServiceTypes();
		final Object object = aFuture.get();
		assertTrue(object instanceof IServiceTypeID[]);
		final IServiceTypeID[] services = (IServiceTypeID[]) object;
		assertTrue("Found: " + services.length + Arrays.asList(services), services.length == 1); // just expect one event as the implementation filters dupes
		for (int i = 0; i < services.length; i++) {
			IServiceTypeID iServiceTypeId = services[i];
			if(serviceInfo.getServiceID().getServiceTypeID().equals(iServiceTypeId)) {
				return;
			}
		}
		fail("Self registered service not found");
	}
}
